package com.jt.resource.aspect;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.resource.annotation.RequiredLog;
import com.jt.resource.pojo.Log;
import com.jt.resource.service.RemoteLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Date;

@Slf4j
@Aspect
@Component
public class LogAspect {
    /**
     * 方法内部不写任何内容，
     * 这个方法主要是为了承载切入点的定义
     * */
    @Pointcut("@annotation(com.jt.resource.annotation.RequiredLog)")
    public void doLog(){
        //方法内部不写任何内容，这个方法主要是为了承载切入点的定义
    }

    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{
        int status = 1;//状态，初始值为1
        long time = 0;//耗时，默认值为0
        String error = "";//
        //1.获取方法开始执行的时间，并输出
        long begin = System.currentTimeMillis();
        log.debug("begin_time {}",begin);
        //2.调用目标执行链，这个执行链的终点是切入点方法，
        //执行结果为切入点方法的执行结果
        try {
            Object result = joinPoint.proceed();
            long end = System.currentTimeMillis();
            log.debug("end_time {}",end);
            time = end - begin;
            return result;
        }catch (Exception e){
            long t3 = System.currentTimeMillis();
            log.debug("error_time",t3);
            time = t3 - begin;
            status = 0;
            error = e.getMessage();
            throw e;
        }finally {
            saveUserLog(joinPoint,time,status,error);//这里保存的是将日志最后传递给system
        }

        //3.获取方法执行结束的数据，并输出

    }

    private void saveUserLog(ProceedingJoinPoint joinPoint,Long time,int status,String error) throws NoSuchMethodException, JsonProcessingException {
        //1.获取用户行为日志
        //1.1 获取ip地址 (首先要获取请求对象，查spring中获取请求对象的方式)
          //RequestContextHolder 对象是当前线程中请求对象的持有者
        ServletRequestAttributes requestAttributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String ip = requestAttributes.getRequest().getRemoteAddr();
        //1.2 获取登陆用户名(借助SecurityContextHolder，此对象中存储了用户的认证信息)
        String username =
                (String) SecurityContextHolder.getContext()
                        .getAuthentication()
                        .getPrincipal();
        //1.3 获取用户操作operation （日志切入点方法上@RequiredLog注解中value的值）
        //1.3.1 获取代理对象对应的目标对象类型
        Class<?> targetCls = joinPoint.getTarget().getClass();
        //1.3.2 获取目标类型中指定的方法（方法信息可通过joinPoint获取方法签名信息）
        MethodSignature methodSignature =  //方法签名-存储了方法信息的一个对象
                (MethodSignature) joinPoint.getSignature();
        Method targetMethod =targetCls.getDeclaredMethod(methodSignature.getName(),
                methodSignature.getParameterTypes());
        //1.3.3 获取方法上的RequiredLog注解
        RequiredLog requiredLog =
                targetCls.getAnnotation(RequiredLog.class);
        //1.3.4获取方法上的RequiredLog注解中的操作名
        String operation = requiredLog.value();
        //1.4 获取目标方法名 targetMethod 这里要求类全名+方法名
        String targetMethodName =
                String.format("%s.%s", targetCls.getName(),
                        targetMethod.getName());
        //1.5 获取请求参数
        // String params=joinPoint.getArgs().toString();//不推荐
        String params = new ObjectMapper().writeValueAsString(joinPoint.getArgs());

        //2.封装用户行为日志
        Log userLog = new Log();
        userLog.setIp(ip);
        userLog.setUsername(username);
        userLog.setCreatedTime(new Date());
        userLog.setOperation(operation);
        userLog.setMethod(targetMethodName);
        userLog.setParams(params);
        userLog.setTime(time);
        userLog.setStatus(status);
        userLog.setError(error);

        //3.持久化用户行为日志
        log.debug("user log: {}",userLog);
        remoteLogService.insertLog(userLog);
    }

    @Autowired
    private RemoteLogService remoteLogService;
}
